<!doctype html>
<html lang="en-US">
  <head>
    <link href="/assets/index.css" rel="stylesheet" type="text/css" />
    <script crossorigin="anonymous" src="/test-harness.js"></script>
    <script crossorigin="anonymous" src="/test-page-object.js"></script>
    <script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
  </head>
  <body>
    <main id="webchat"></main>
    <script type="importmap">
      {
        "imports": {
          "@testduet/wait-for": "https://unpkg.com/@testduet/wait-for@main/dist/wait-for.mjs",
          "jest-mock": "https://esm.sh/jest-mock",
          "react-dictate-button/internal": "https://unpkg.com/react-dictate-button@main/dist/react-dictate-button.internal.mjs"
        }
      }
    </script>
    <script type="module">
      import { waitFor } from '@testduet/wait-for';
      import { fn, spyOn } from 'jest-mock';
      import {
        SpeechGrammarList,
        SpeechRecognition,
        SpeechRecognitionAlternative,
        SpeechRecognitionEvent,
        SpeechRecognitionResult,
        SpeechRecognitionResultList
      } from 'react-dictate-button/internal';
      import { SpeechSynthesis, SpeechSynthesisEvent, SpeechSynthesisUtterance } from './js/index.js';

      const {
        testHelpers: { createDirectLineEmulator },
        WebChat: { renderWebChat, testIds }
      } = window;

      run(async function () {
        const speechSynthesis = new SpeechSynthesis();
        const ponyfill = {
          SpeechGrammarList,
          SpeechRecognition: fn().mockImplementation(() => {
            const speechRecognition = new SpeechRecognition();

            spyOn(speechRecognition, 'start');

            return speechRecognition;
          }),
          speechSynthesis,
          SpeechSynthesisUtterance
        };

        spyOn(speechSynthesis, 'speak');

        const { directLine, store } = createDirectLineEmulator();

        renderWebChat(
          {
            directLine,
            store,
            webSpeechPonyfillFactory: () => ponyfill
          },
          document.getElementById('webchat')
        );

        await pageConditions.uiConnected();

        // WHEN: Microphone button is clicked.
        await pageObjects.clickMicrophoneButton();

        // THEN: Should create new SpeechSynthesis() for priming user gesture requirement.
        await waitFor(() => expect(speechSynthesis.speak).toHaveBeenCalledTimes(1));
        expect(speechSynthesis).toHaveProperty('paused', false);
        expect(speechSynthesis).toHaveProperty('pending', false);
        expect(speechSynthesis).toHaveProperty('speaking', true);
        expect(speechSynthesis.speak).toHaveBeenLastCalledWith(expect.any(SpeechSynthesisUtterance));
        expect(speechSynthesis.speak).toHaveBeenLastCalledWith(expect.objectContaining({ text: '' }));

        // WHEN: Priming is done.
        speechSynthesis.speak.mock.calls[0][0].dispatchEvent(
          new SpeechSynthesisEvent('end', { utterance: speechSynthesis.speak.mock.calls[0] })
        );
        expect(speechSynthesis).toHaveProperty('paused', false);
        expect(speechSynthesis).toHaveProperty('pending', false);
        expect(speechSynthesis).toHaveProperty('speaking', false);

        // THEN: Should construct SpeechRecognition().
        await waitFor(() => expect(ponyfill.SpeechRecognition).toHaveBeenCalledTimes(1));

        const { value: speechRecognition1 } = ponyfill.SpeechRecognition.mock.results[0];

        // THEN: Should call SpeechRecognition.start().
        expect(speechRecognition1.start).toHaveBeenCalledTimes(1);

        // THEN: Send box should say "Listening…" and hide the original text box.
        expect(pageElements.sendBoxTextBox()).toBeFalsy();
        expect(document.querySelector(`[data-testid="${testIds.sendBoxSpeechBox}"]`)).toHaveProperty(
          'textContent',
          'Starting…'
        );

        // WHEN: Recognition started and interims result is dispatched.
        speechRecognition1.dispatchEvent(new Event('start'));
        speechRecognition1.dispatchEvent(new Event('audiostart'));
        speechRecognition1.dispatchEvent(new Event('soundstart'));
        speechRecognition1.dispatchEvent(new Event('speechstart'));

        await (
          await directLine.actPostActivity(async () => {
            speechRecognition1.dispatchEvent(
              new SpeechRecognitionEvent('result', {
                results: new SpeechRecognitionResultList(
                  new SpeechRecognitionResult(new SpeechRecognitionAlternative(0, 'Hello'))
                )
              })
            );

            const [interims] = document.getElementsByClassName('webchat__send-box__dictation-interims');

            // THEN: Interims should be "Hello".
            await waitFor(() => expect(interims.textContent.trimEnd()).toBe('Hello'));

            // WHEN: Interims result is dispatched.
            speechRecognition1.dispatchEvent(
              new SpeechRecognitionEvent('result', {
                results: new SpeechRecognitionResultList(
                  new SpeechRecognitionResult(new SpeechRecognitionAlternative(0, 'Hello, World!'))
                )
              })
            );

            // THEN: Interims should be "Hello, World!".
            await waitFor(() => expect(interims.textContent.trimEnd()).toBe('Hello, World!'));

            // WHEN: Final result is dispatched.
            speechRecognition1.dispatchEvent(
              new SpeechRecognitionEvent('result', {
                results: new SpeechRecognitionResultList(
                  SpeechRecognitionResult.fromFinalized(new SpeechRecognitionAlternative(0.9, 'Hello, World!'))
                )
              })
            );

            speechRecognition1.dispatchEvent(new Event('speechend'));
            speechRecognition1.dispatchEvent(new Event('soundend'));
            speechRecognition1.dispatchEvent(new Event('audioend'));
            speechRecognition1.dispatchEvent(new Event('end'));
          })
        ).resolveAll();

        // THEN: Should have send the activity.
        await pageConditions.numActivitiesShown(1);
        expect(pageElements.activityContents()[0]).toHaveProperty('textContent', 'Hello, World!');

        // THEN: Send box go back to input mode.
        expect(pageElements.sendBoxTextBox()).toBeTruthy();

        // WHEN: Bot replied.
        await directLine.emulateIncomingActivity({
          inputHint: 'expectingInput', // "expectingInput" should turn the microphone back on after synthesis completed.
          text: 'Aloha!',
          type: 'message'
        });

        // THEN: Should send a message and the reply from the bot.
        await pageConditions.numActivitiesShown(2);
        expect(pageElements.activityContents()[1]).toHaveProperty('textContent', 'Aloha!');

        // THEN: Should call SpeechSynthesis.speak() again.
        await waitFor(() => expect(speechSynthesis.speak).toHaveBeenCalledTimes(2));

        // THEN: Should start synthesize "Aloha!".
        expect(speechSynthesis.speak).toHaveBeenLastCalledWith(expect.any(SpeechSynthesisUtterance));
        expect(speechSynthesis.speak).toHaveBeenLastCalledWith(expect.objectContaining({ text: 'Aloha!' }));

        // THEN: SpeechSynthesis.speaking should be true.
        expect(speechSynthesis).toHaveProperty('paused', false);
        expect(speechSynthesis).toHaveProperty('pending', false);
        expect(speechSynthesis).toHaveProperty('speaking', true);

        // WHEN: Synthesis completed.
        speechSynthesis.speak.mock.calls[1][0].dispatchEvent(
          new SpeechSynthesisEvent('end', { utterance: speechSynthesis.speak.mock.calls[1] })
        );

        // THEN: SpeechSynthesis.speaking should be false.
        expect(speechSynthesis).toHaveProperty('paused', false);
        expect(speechSynthesis).toHaveProperty('pending', false);
        expect(speechSynthesis).toHaveProperty('speaking', false);

        // THEN: Should create new SpeechRecognition() again as "expectingInput" should restart speech recognition.
        await waitFor(() => expect(ponyfill.SpeechRecognition).toHaveBeenCalledTimes(2));

        const { value: speechRecognition2 } = ponyfill.SpeechRecognition.mock.results[1];

        // THEN: Should have called SpeechRecognition.start().
        expect(speechRecognition2.start).toHaveBeenCalledTimes(1);

        await (
          await directLine.actPostActivity(async () => {
            // WHEN: Recognized as "Good morning!" without interims.
            speechRecognition2.dispatchEvent(new Event('start'));
            speechRecognition2.dispatchEvent(new Event('audiostart'));
            speechRecognition2.dispatchEvent(new Event('soundstart'));
            speechRecognition2.dispatchEvent(new Event('speechstart'));
            speechRecognition2.dispatchEvent(
              new SpeechRecognitionEvent('result', {
                results: new SpeechRecognitionResultList(
                  SpeechRecognitionResult.fromFinalized(new SpeechRecognitionAlternative(0.9, 'Good morning!'))
                )
              })
            );
            speechRecognition2.dispatchEvent(new Event('speechend'));
            speechRecognition2.dispatchEvent(new Event('soundend'));
            speechRecognition2.dispatchEvent(new Event('audioend'));
            speechRecognition2.dispatchEvent(new Event('end'));
          })
        ).resolveAll();

        // THEN: Should have send another activity.
        await pageConditions.numActivitiesShown(3);
        expect(pageElements.activityContents()[2]).toHaveProperty('textContent', 'Good morning!');
      });
    </script>
  </body>
</html>
